/*
 *  $Id: LanguageList.cs 1181 2010-11-10 16:08:03Z thenn.erannor $
 *	This file is a part of Ragima CCG.
 *	(C) 2008-2010 Ragima development group.
 *
 *	Licensed under the terms of the GNU GPL License version 2.
 *
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

using Nereal.Extensions;

namespace Ragima.Languages {
    /// <summary>
    /// Список языков, идентифируемых по строковым кодам
    /// </summary>
    public class LanguageList {
        /// <summary>
        /// Код языка по умолчанию.
        /// </summary>
        public const string DefaultLanguageCode = "ru";

        private Dictionary<string, Language> _langs;
        private Dictionary<Type, Language> _typeMap;

        public LanguageList() {
            _langs = new Dictionary<string, Language>();
            _typeMap = new Dictionary<Type, Language>();
        }

        /// <summary>
        /// Количество языков.
        /// </summary>
        public int Count {
            get { return _langs.Count; }
        }

        /// <summary>
        /// Добавляет язык язык в список.
        /// </summary>
        public void AddLanguage(Language lang) {
            _langs[lang.LangCode] = lang;
        }

        /// <summary>
        /// Добавляет языки из найденных сборок по указанному пути.
        /// </summary>
        public void AddLanguages(string assemblyPath, bool recurse) {
            foreach (var filename in Directory.GetFiles(assemblyPath, "*.dll", recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) {
                var assembly = Assembly.LoadFrom(filename);
                ScanTypes(assembly);
            }
        }

        /// <summary>
        /// Сканирует типы в указанной сборке и добавляет их к языковой системе.
        /// </summary>
        private void ScanTypes(Assembly assembly) {
            var localizedInterfaceType = typeof(ILocalized);
            var localizerFactoryInterfaceType = typeof(ILocalizerFactory);
            foreach (var type in assembly.GetTypes()) {
                if (type.IsAbstract || type.IsNested)
                    continue;
                if (type.Implements(localizedInterfaceType) && type.HasAttribute<ResourceLangAttribute>())
                    AddLocalizableType(type);
                if (type.Implements(localizerFactoryInterfaceType))
                    AddLocalizerFactory(type);
            }
        }

        /// <summary>
        /// Добавляет локализованную версию типа.
        /// </summary>
        private void AddLocalizableType(Type locType) {
            var attr = locType.GetAttribute<ResourceLangAttribute>();
            var code = attr != null ? attr.LangCode : DefaultLanguageCode;
            var lang = GetOrAddLanguage(code);
            _typeMap.Add(locType, lang);
            var baseType = locType.BaseType;
            while (baseType != null && baseType != typeof(object)) {
                lang.AddLocalizedType(baseType, locType);
                baseType = baseType.BaseType;
            }
        }

        /// <summary>
        /// Добавляет фабрику локализеров.
        /// </summary>
        private void AddLocalizerFactory(Type factoryType) {
            var factory = (ILocalizerFactory) Activator.CreateInstance(factoryType);
            foreach (var localizer in factory.GetLocalizers()) {
                var lang = GetOrAddLanguage(localizer.LangCode);
                lang.AddLocalizer(localizer);
            }
        }

        /// <summary>
        /// Получает язык по строковому коду, или добавляет его в случае отсутствия.
        /// </summary>
        private Language GetOrAddLanguage(string code) {
            var lang = GetLanguage(code);
            if (lang == null) {
                lang = new Language(code);
                AddLanguage(lang);
            }
            return lang;
        }

        /// <summary>
        /// Получает язык по строковому коду.
        /// </summary>
        /// <returns>Если в списке таковой отсутствует, то null.</returns>
        public Language GetLanguage(string code) {
            return _langs.GetOrDefault(code, null);
        }

        /// <summary>
        /// Получает язык по умолчанию.
        /// </summary>
        public Language GetDefaultLanguage() {
            if (_langs.ContainsKey(DefaultLanguageCode))
                return _langs[DefaultLanguageCode];
            else if (_langs.Count > 0)
                foreach (var lang in _langs.Values)
                    return lang;
            return null;
        }

        /// <summary>
        /// Получает список всех строковых кодов языков.
        /// </summary>
        public IEnumerable<string> GetLanguageCodes() {
            return _langs.Keys;
        }

        /// <summary>
        /// Получает список всех языков.
        /// </summary>
        public IEnumerable<Language> GetLanguages() {
            return _langs.Values;
        }

        /// <summary>
        /// Получает язык по типу локализованного ресурса.
        /// </summary>
        public Language GetLanguage<T>() where T : ILocalized {
            return GetLanguage(typeof(T));
        }

        /// <summary>
        /// Получает язык по локализованному ресурсу.
        /// </summary>
        public Language GetLanguage<T>(T resource) where T : ILocalized {
            return GetLanguage(resource.GetType());
        }

        /// <summary>
        /// Получает язык по типу локализованного ресурса.
        /// </summary>
        public Language GetLanguage(Type resourceType) {
            return _typeMap.GetOrDefault(resourceType, null) ?? GetDefaultLanguage();
        }
    }
}
